package cn.wolfcode.p2p.business.service.impl;

import java.math.RoundingMode;
import java.util.*;

import cn.wolfcode.p2p.base.domain.BaseAuditDomain;
import cn.wolfcode.p2p.base.service.ISystemDictionaryService;
import cn.wolfcode.p2p.business.domain.*;
import cn.wolfcode.p2p.business.query.BidRequestAuditQueryObject;
import cn.wolfcode.p2p.base.domain.Account;
import cn.wolfcode.p2p.base.domain.LoginInfo;
import cn.wolfcode.p2p.base.domain.UserInfo;
import cn.wolfcode.p2p.base.service.IAccountService;
import cn.wolfcode.p2p.base.service.IUserInfoService;
import cn.wolfcode.p2p.business.mapper.BidRequestMapper;
import cn.wolfcode.p2p.business.query.ExpAccountQueryObject;
import cn.wolfcode.p2p.business.service.*;
import cn.wolfcode.p2p.enums.OrderByColumn;
import cn.wolfcode.p2p.enums.OrderType;
import cn.wolfcode.p2p.util.*;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.concurrent.ConcurrentLinkedDeque;

@Service
@Transactional
public class BidRequestServiceImpl implements IBidRequestService, ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private IUserInfoService userInfoService;
    @Autowired
    private IAccountService accountService;
    @Autowired
    private BidRequestMapper bidRequestMapper;
    @Autowired
    private IBidRequestAuditHistoryService historyService;
    @Autowired
    private IBidService bidService;
    @Autowired
    private IAccountFlowService accountFlowService;
    @Autowired
    private ISystemAccountService systemAccountService;
    @Autowired
    private ISystemAccountFlowService systemAccountFlowService;
    @Autowired
    private IPaymentScheduleService paymentScheduleService;
    @Autowired
    private IPaymentScheduleDetailService paymentScheduleDetailService;
    @Autowired
    private ICreditTransferService creditTransferService;
    @Autowired
    private IExpAccountService expAccountService;

    //定时发标队列
    private static final ConcurrentLinkedDeque publishBidRequestQueue = new ConcurrentLinkedDeque();


    //定义一个定时器(间隔时间大一点),专门负责查询数据库中的代发标,放入队列(存在重复放入问题)
    @Override
    public void publishBidRequestToQueue() {
        BidRequestAuditQueryObject qo = new BidRequestAuditQueryObject();
        //待发布
        qo.setState(Constants.BIDREQUEST_STATE_PUBLISH_PENDING);
        //不设置分页
        qo.setPageSize(-1);
        //查询所有代发标的放入队列中
        List<BidRequest> publishBidRequests = bidRequestMapper.listForPage(qo);
        for (BidRequest br : publishBidRequests) {
            //判断数据库中没有重复的才把数据放进队列中
            if (!publishBidRequestQueue.contains(br)) {
                publishBidRequestQueue.offer(br);
            }
        }
    }

    //启动系统时,查询数据库中的代发标,放入队列(考虑重复放入问题)
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        publishBidRequestToQueue();
    }


    @Override
    public void publishBidRequestCheck() {
        Iterator<BidRequest> it = publishBidRequestQueue.iterator();
        Date now = new Date();
        while (it.hasNext()) {
            BidRequest br = it.next();
            if (br.getPublishTime().before(now)) {
                //如果发布时间在now之前,立即执行发标(把状态改为发标中)
                br.setBidRequestState(Constants.BIDREQUEST_STATE_BIDDING);
                update(br);
                //移除已经发标的队列中的信息,需要用迭代器移除
                it.remove();
            }

        }
    }

    @Override
    public void publishAudit(Long id, String remark, Integer state, Date publishTime) {
        //参数判断
        //判断借款对象的状态要处于待审核
        BidRequest bidRequest = bidRequestMapper.selectByPrimaryKey(id);
        //设置审核相关信息
        Assert.isFalse(bidRequest.getBidRequestState() != Constants.BIDREQUEST_STATE_APPLYING,
                "不在待审核状态中");
        historyService.save(bidRequest, remark, state, BidRequestAuditHistory.AUDITTYPE_PUBLISH);

        //风控备注
        bidRequest.setNote(remark);

        if (state == BaseAuditDomain.STATE_SUCCESS) {
            if (publishTime == null) {
                //没有选择发标时间: 立即发标
                //招标中的状态
                bidRequest.setBidRequestState(Constants.BIDREQUEST_STATE_BIDDING);
                //发布时间
                bidRequest.setPublishTime(new Date());

            } else {
                //2.如果有选择发标时间:定时发标
                //改为待发布状态
                bidRequest.setBidRequestState(Constants.BIDREQUEST_STATE_PUBLISH_PENDING);
                //设置发标时间
                bidRequest.setPublishTime(publishTime);
            }
            //招标截止时间
            bidRequest.setDisableDate(DateUtils.addDays(bidRequest.getPublishTime(), bidRequest.getDisableDays()));

        } else {
            //审核失败
            //1.借款状态设置为发标审核拒绝
            bidRequest.setBidRequestState(Constants.BIDREQUEST_STATE_PUBLISH_REFUSE);

            //2.申请人的位状态移除
            UserInfo userInfo = userInfoService.getById(bidRequest.getCreateUser().getId());
            userInfo.removeState(BitStatesUtils.HAS_BIDREQUEST_IN_PROCESS);
            userInfoService.update(userInfo);
        }
        update(bidRequest);

        //如果后台有一个代发标审核,立即放入队列
        if (state == BaseAuditDomain.STATE_SUCCESS && publishTime != null) {
            bidRequest.setVersion(bidRequest.getVersion() + 1);
            publishBidRequestQueue.offer(bidRequest);
        }
    }


    private void update(BidRequest bidRequest) {
        Assert.isFalse(0 == bidRequestMapper.updateByPrimaryKey(bidRequest), "借款信息修改失败[乐观锁失败]");
    }


    @Override
    public BidRequest getById(Long id) {
        return bidRequestMapper.selectByPrimaryKey(id);
    }


    @Override
    public void apply(BidRequest bidRequest) {
        //当前用户
        LoginInfo applier = UserContext.getLoginInfo();
        //用户的账户
        Account account = accountService.getById(applier.getId());

        //1.借款金额要大于等于系统规定的最小金额
        Assert.isFalse(bidRequest.getBidRequestAmount().compareTo(Constants.BORROW_MIN_AMOUNT) < 0,
                "借款不能少于最小金额:" + Constants.BORROW_MIN_AMOUNT);

        //2.借款金额要小于等于用户的剩余信用额度
        Assert.isFalse(bidRequest.getBidRequestAmount().compareTo(Constants.BORROWLIMIT) > 0,
                "剩余信用额度为:" + Constants.BORROWLIMIT);

        //3.利率大于等于系统规定的最小利率
        Assert.isFalse(bidRequest.getCurrentRate().compareTo(Constants.MIN_CURRENTRATE) < 0,
                "最小利率为:" + Constants.MIN_CURRENTRATE);

        //4.利率要小于等于系统规定的最大利率
        Assert.isFalse(bidRequest.getCurrentRate().compareTo(Constants.MAX_CURRENTRATE) > 0,
                "最大利率为:" + Constants.MAX_CURRENTRATE);

        //5.判断借款期限,是否合法
        Assert.isFalse(!Constants.BORROW_RETURN_MONTHS.contains(bidRequest.getReturnMonthes().toString()),
                "非法的借款期限");

        //6.最小投标要大于等于系统规定的最小投标
        Assert.isFalse(bidRequest.getMinBidAmount().compareTo(Constants.BID_MIN_AMOUNT) < 0,
                "投标不能少于最小金额:" + Constants.BID_MIN_AMOUNT);

        //7.最小投标不能超过借款金额的 50%
        BigDecimal tempAmount = bidRequest.getBidRequestAmount().multiply(new BigDecimal(0.5));
        Assert.isFalse(bidRequest.getMinBidAmount().compareTo(tempAmount) > 0,
                "投标不能大于总额的50%:" + tempAmount);

        //8.判断招标天数是否合法
        Assert.isFalse(!Constants.BID_DISABLE_DAYS.contains(bidRequest.getDisableDays().toString()),
                "非法的招标天数");

        //9.判断标题不可为空
        Assert.notNull(bidRequest.getTitle(), "标题不可为空");

        //10.判断备注不可为空
        Assert.notNull(bidRequest.getDescription(), "备注不可为空");

        //11.判断还款方式
        Assert.isFalse(bidRequest.getReturnType() != Constants.RETURN_TYPE_MONTH_INTEREST &&
                        bidRequest.getReturnType() != Constants.RETURN_TYPE_MONTH_INTEREST_PRINCIPAL,
                "非法的还款方式");

        //2.判断是否有借款在申请流程中
        UserInfo userInfo = userInfoService.getById(applier.getId());
        Assert.isFalse(userInfo.hasBidRequestInProcess(), "亲,您的借款在申请流程当中");
        //3.判断是否完成三个借款条件
        Assert.isFalse(!userInfo.canBorrow(), "不满足三个条件");
        //4.保存一个借款申请对象,new一个对象直接设值
        BidRequest br = new BidRequest();
        br.setReturnType(bidRequest.getReturnType());
        //借款类型
        br.setBidRequestType(Constants.BIDREQUEST_TYPE_NORMAL);
        br.setBidRequestState(Constants.BIDREQUEST_STATE_APPLYING);
        br.setBidRequestAmount(bidRequest.getBidRequestAmount());
        br.setCurrentRate(bidRequest.getCurrentRate());
        br.setMinBidAmount(bidRequest.getMinBidAmount());
        br.setReturnMonthes(bidRequest.getReturnMonthes());
        br.setDisableDays(bidRequest.getDisableDays());
        br.setTitle(bidRequest.getTitle());
        br.setDescription(bidRequest.getDescription());
        br.setCreateUser(applier);
        br.setApplyTime(new Date());
        //总利息
        BigDecimal totalIntrest = CalculatetUtil.calTotalInterest(br.getReturnType(), br.getBidRequestAmount(),
                br.getCurrentRate(), br.getReturnMonthes());
        br.setTotalRewardAmount(totalIntrest);
        bidRequestMapper.insert(br);

        //5.申请人的userInfo.bitSate加上 借款在申请流程中的位状态
        userInfo.addState(BitStatesUtils.HAS_BIDREQUEST_IN_PROCESS);
        userInfoService.update(userInfo);

    }

    @Override
    public PageInfo listForPage(BidRequestAuditQueryObject qo) {

        PageHelper.startPage(qo.getCurrentPage(), qo.getPageSize());
        List list = bidRequestMapper.listForPage(qo);
        return new PageInfo(list);
    }


    @Override
    public List<BidRequest> listPublishPendngBidRequests() {
        //待发标的  5个标,按照发标时间先后排序
        BidRequestAuditQueryObject qo = new BidRequestAuditQueryObject();
        //获取待发标的状态
        qo.setState(Constants.BIDREQUEST_STATE_PUBLISH_PENDING);

        //设置一页显示5个标
        //qo.setPageSize(5);

        //排序
        qo.setOrderBy(OrderByColumn.PUBLISH_TIME.getColumn());
        qo.setOrderType(OrderType.ASC.name());

        return bidRequestMapper.listForPage(qo);
    }

    @Override
    public List<BidRequest> listInvestBidRequests() {
        //1，投标中；2，还款中；3已完成的标的5个标；注意，按照投标中>还款中>已完成的顺序排列；
        BidRequestAuditQueryObject qo = new BidRequestAuditQueryObject();
        qo.setBidRequestState(new int[]{Constants.BIDREQUEST_STATE_BIDDING,
                Constants.BIDREQUEST_STATE_PAYING_BACK, Constants.BIDREQUEST_STATE_COMPLETE_PAY_BACK});
        qo.setOrderBy(OrderByColumn.PUBLISH_TIME.getColumn());
        qo.setOrderType(OrderType.ASC.name());

        return bidRequestMapper.listForPage(qo);
    }


    @Override
    public void bid(Long bidRequestId, BigDecimal amount) {
        //判断
        //1.判断借款对象状态是否处于招标中
        //获取借款对象
        BidRequest br = bidRequestMapper.selectByPrimaryKey(bidRequestId);
        Assert.isFalse(br.getBidRequestState() != Constants.BIDREQUEST_STATE_BIDDING, "当前状态不处于招标中");
        //2.如果剩余可投标不小于最小投标 ，判断投标金额要大于等于最小投标
        if (br.getRemainAmount().compareTo(br.getMinBidAmount()) >= 0) {
            Assert.isFalse(amount.compareTo(br.getMinBidAmount()) < 0, "最小投标金额为" + br.getMinBidAmount());
        } else {
            //  否则，允许一次性把剩下的可投标投完
            Assert.isFalse(amount.compareTo(br.getRemainAmount()) != 0, "当前可投标金额为" + br.getRemainAmount());
        }

        //3.当前用户对于这个借款的总投标金额不能超过借款金额的 50%
        LoginInfo loginInfo = UserContext.getLoginInfo();
        BigDecimal totalBidAmount = bidService.getTotalBidAmountByUserIdAndBidRequestId(loginInfo.getId(), br.getId());
        totalBidAmount = totalBidAmount.add(amount);
        Assert.isFalse(totalBidAmount.compareTo(br.getBidRequestAmount().multiply(new BigDecimal(0.5))) > 0,
                "当前总投标金额不能超过借款金额的 50%");

        //4.判断可用余额要大于等于当前投标金额
        Account investor = accountService.getById(UserContext.getLoginInfo().getId());
        Assert.isFalse(investor.getUsableAmount().compareTo(amount) < 0, "当前账户余额不足,请充值后再投标");

        //5.当前投标金额不能超过剩余可投标
        Assert.isFalse(amount.compareTo(br.getRemainAmount()) > 0, "当前可投标金额为" + br.getRemainAmount());
        //6.当前投标 用户不能是借款人
        Assert.isFalse(loginInfo.getId().compareTo(br.getCreateUser().getId()) == 0, "您无权利投自己的标");

        //执行投标
        //1.可用余额要减少
        investor.subtractUsableAmount(amount);
        //2.冻结金额增加
        investor.addFreezedAmount(amount);
        //更新账户信息
        accountService.update(investor);

        //3.创建投标冻结流水
        accountFlowService.createBidFreezedFlow(investor, amount, br.getTitle());
        //4.保存一个投标对象
        bidService.save(br, amount);
        //5.借款对象的 投标次数增加
        br.setBidCount(br.getBidCount() + 1);
        //6.借款对象的 投标总额增加
        br.setCurrentSum(br.getCurrentSum().add(amount));

        //后续
        if (br.getCurrentSum().compareTo(br.getBidRequestAmount()) == 0) {
            //如果满标，借款对象状态修改为满标一审
            br.setBidRequestState(Constants.BIDREQUEST_STATE_APPROVE_PENDING_1);
            //这个借款下的所有投标记录的状态修改为满标一审
            bidService.batchUpdateStateByBidRequestId(br.getId(), Constants.BIDREQUEST_STATE_APPROVE_PENDING_1);
        }
        update(br);
    }

    @Override
    public void fullAudit1(Long id, String remark, Integer state) {
        //判断
        //1.判断借款对象状态要处于满标一审
        //获取借款对象
        BidRequest br = bidRequestMapper.selectByPrimaryKey(id);
        Assert.isFalse(br.getBidRequestState() != Constants.BIDREQUEST_STATE_APPROVE_PENDING_1,
                "当前审核状态不处于满标一审");
        //2.设置审核相关信息(BidRequestAuditHistory)
        historyService.save(br, remark, state, BidRequestAuditHistory.AUDITTYPE_AUDIT1);

        //3.如果审核成功：
        if (state == BaseAuditDomain.STATE_SUCCESS) {
            //1.修改借款对象状态进入满标二审
            br.setBidRequestState(Constants.BIDREQUEST_STATE_APPROVE_PENDING_2);
            //2.这个借款下的所有投标记录的状态修改为满标二审
            bidService.batchUpdateStateByBidRequestId(br.getId(), Constants.BIDREQUEST_STATE_APPROVE_PENDING_2);

        } else {
            fullAuditReject(br);
        }
        update(br);
    }

    private void fullAuditReject(BidRequest br) {
        //4.如果审核失败：
        //1.修改借款对象状态为满标审核拒绝
        br.setBidRequestState(Constants.BIDREQUEST_STATE_REJECTED);

        //2.这个借款下的所有投标记录的状态修改为满标审核拒绝
        bidService.batchUpdateStateByBidRequestId(br.getId(), Constants.BIDREQUEST_STATE_REJECTED);

        //3.退钱：这个借款的所有的投标人
        List<Bid> bids = br.getBids();

        //当数据库中存在多个相同的id,不断的连接数据库查询会降低性能
        //map用来缓存投资账户的id
        Map<Long, Account> bidAccounts = new HashMap<>();
        for (Bid bid : bids) {
            //投资账户
            Long bidUserId = bid.getBidUser().getId();
            Account bidAccount = bidAccounts.get(bidUserId);
            if (bidAccount == null) {
                bidAccount = accountService.getById(bidUserId);
                bidAccounts.put(bidUserId, bidAccount);
            }
            // 冻结金额减少
            bidAccount.subtractFreezedAmount(bid.getAvailableAmount());
            // 可用余额增加
            bidAccount.addUsableAmount(bid.getAvailableAmount());
            //在循环里更新,更加能细分化每一笔账
            //accountService.update(bidAccount);
            //4.创建投标失败，创建解冻流水
            accountFlowService.createAccountErrorFlow(bidAccount, bid.getAvailableAmount(), br.getTitle());
        }
        //统一修改投资人账户
        for (Account bidAccount : bidAccounts.values()) {
            accountService.update(bidAccount);
        }

        //5.借款人是否有借款在申请流程中的位状态移除
        UserInfo borrowUserInfo = userInfoService.getById(br.getCreateUser().getId());
        borrowUserInfo.removeState(BitStatesUtils.HAS_BIDREQUEST_IN_PROCESS);
        userInfoService.update(borrowUserInfo);
    }

    @Override
    public void fullAudit2(Long id, String remark, Integer state) {

        //1.判断借款对象状态要处于满标二审
        //获取借款对象
        BidRequest br = bidRequestMapper.selectByPrimaryKey(id);
        Assert.isFalse(br.getBidRequestState().compareTo(Constants.BIDREQUEST_STATE_APPROVE_PENDING_2) != 0,
                "当前状态不处于满标二审中");

        //2.设置审核相关信息(BidRequestAuditHistory)
        historyService.save(br, remark, state, BidRequestAuditHistory.AUDITTYPE_AUDIT2);

        //3.如果审核失败：同一审失败

        if (state == BaseAuditDomain.STATE_SUCCESS) {
            //4.如果审核成功：
            //投资人
            List<Bid> bids = br.getBids();
            HashMap<Long, Account> bidAccounts = new HashMap<>();
            for (Bid bid : bids) {
                //获取账户id
                Long bidUserId = bid.getBidUser().getId();
                Account bidAccount = bidAccounts.get(bidUserId);
                if (bidAccount == null) {
                    bidAccount = accountService.getById(bidUserId);
                    bidAccounts.put(bidUserId, bidAccount);
                }
                //1.冻结金额减少
                bidAccount.subtractFreezedAmount(bid.getAvailableAmount());
                //2.创建投标成功，解冻流水
                accountFlowService.createBidSuccessFlow(bidAccount, bid.getAvailableAmount(), br.getTitle());
                //3.投资人待收本金增加
                bidAccount.setUnReceivePrincipal(bidAccount.getUnReceivePrincipal().add(bid.getAvailableAmount()));
                //4.投资人待收利息增加:当前这个bid产生的利息=当前这个bid的投标金额/借款的总金额*借款的总利息
                BigDecimal interest = bid.getAvailableAmount().divide(br.getBidRequestAmount(), Constants.SCALE_CALCULATE, RoundingMode.HALF_UP).
                        multiply(br.getTotalRewardAmount());
                //设置精度
                bidAccount.setUnReceiveInterest(bidAccount.getUnReceiveInterest().add(interest.setScale(Constants.SCALE_SAVE, RoundingMode.HALF_UP)));
            }
            //统一修改收款人账户
            for (Account account : bidAccounts.values()) {
                accountService.update(account);
            }
            //借款人
            Long borrowUserId = br.getCreateUser().getId();
            Account borrowUser = accountService.getById(borrowUserId);
            //1.可用余额增加收到借款
            borrowUser.addUsableAmount(br.getBidRequestAmount());
            //2.创建成功借款流水
            accountFlowService.createBorrowSuccessFlow(borrowUser, br.getBidRequestAmount(), br.getTitle());

            //3.信用额度减少
            borrowUser.setRemainBorrowLimit(borrowUser.getRemainBorrowLimit().subtract(br.getBidRequestAmount()));
            //4.待还本息增加
            borrowUser.setUnReturnAmount(borrowUser.getUnReturnAmount().add(br.getBidRequestAmount().add(br.getTotalRewardAmount())));
            //5.移除借款人是否有借款在申请流程中的位状态
            UserInfo borrowUserInfo = userInfoService.getById(borrowUserId);
            borrowUserInfo.removeState(BitStatesUtils.HAS_BIDREQUEST_IN_PROCESS);
            userInfoService.update(borrowUserInfo);

            //6.支付平台借款手续费 ： 可用余额减少
            BigDecimal accountManagementCharge = CalculatetUtil.calAccountManagementCharge(br.getBidRequestAmount());
            borrowUser.subtractUsableAmount(accountManagementCharge);
            accountService.update(borrowUser);
            //7.创建支付借款手续费流水
            accountFlowService.createPayAccountManagementChargeFlow(borrowUser, accountManagementCharge, br.getTitle());

            //获取平台账户
            SystemAccount systemAccount = systemAccountService.getCurrent();
            //1.收取借款手续费：可用余额增加
            systemAccount.setUsableAmount(systemAccount.getUsableAmount().add(accountManagementCharge));
            //2.创建平台账户收取借款手续费流水
            systemAccountFlowService.createReceiveAccountManagementChargeFlow(systemAccount, accountManagementCharge, br.getTitle());
            systemAccountService.update(systemAccount);

            //创建还款计划 and  创建收款计划
            paymentScheduleService.createPaymentsSchedule(br);

            //借款对象状态修改为还款中
            br.setBidRequestState(Constants.BIDREQUEST_STATE_PAYING_BACK);
            //这个借款下的所有投标记录的状态修改为还款中
            bidService.batchUpdateStateByBidRequestId(br.getId(), Constants.BIDREQUEST_STATE_PAYING_BACK);

        } else {
            fullAuditReject(br);
        }

        update(br);
    }

    @Override
    public void returnMoney(Long id) {

        //还款逻辑
        //判断
        //1.获取还款对象,判断还款计划状态是否处于待还款
        PaymentSchedule ps = paymentScheduleService.getById(id);
        Assert.isFalse(ps.getState() == Constants.PAYMENT_STATE_DONE, "当前状还款态已还清");
        //2.判断还款人的余额是否足够
        Account borrowAccount = accountService.getById(ps.getBorrowUser().getId());
        Assert.isFalse(borrowAccount.getUsableAmount().compareTo(ps.getTotalAmount()) < 0, "余额不足,请充值");
        //3.判断登录用户必须是借款人
        Assert.isFalse(!UserContext.getLoginInfo().getId().equals(ps.getBorrowUser().getId()), "当前还款人必须和借款人一致");

        //还款人
        BigDecimal returnAmount = ps.getTotalAmount();
        //1.执行还款，可用余额减少
        borrowAccount.subtractUsableAmount(returnAmount);
        //2.创建还款成功流水
        accountFlowService.createReturnMoneySuccessFlow(borrowAccount, returnAmount, ps.getBidRequestTitle());
        //3.信用额度增加
        borrowAccount.setRemainBorrowLimit(borrowAccount.getRemainBorrowLimit().add(ps.getPrincipal()));
        //4.待还总额减少
        borrowAccount.setUnReturnAmount(borrowAccount.getUnReturnAmount().subtract(returnAmount));

        accountService.update(borrowAccount);

        //5.当期还款计划的状态修改为已还
        ps.setState(Constants.PAYMENT_STATE_DONE);
        //6.设置当期还款计划的还款时间
        ps.setPayDate(new Date());
        paymentScheduleService.update(ps);

        //收款人
        //收款计划列表
        List<PaymentScheduleDetail> scheduleDetails = ps.getPaymentScheduleDetails();
        //平台账户
        SystemAccount systemAccount = systemAccountService.getCurrent();

        //收款人缓存
        HashMap<Long, Account> bidAccounts = new HashMap<>();
        for (PaymentScheduleDetail detail : scheduleDetails) {
            //收款人id
            Long receiverId = detail.getToLoginInfoId();
            //收款人账户
            Account bidAccount = bidAccounts.get(receiverId);
            if (bidAccount == null) {
                bidAccount = accountService.getById(receiverId);
                bidAccounts.put(receiverId, bidAccount);
            }
            //1.执行收款，可用余额增加
            bidAccount.addUsableAmount(detail.getTotalAmount());
            //2.创建收款成功流水
            accountFlowService.createReceiveMonetSuccessFlow(bidAccount, detail.getTotalAmount(), ps);
            //3.支付平台利息手续费
            BigDecimal interestManagerCharge = CalculatetUtil.calInterestManagerCharge(detail.getInterest());
            bidAccount.subtractUsableAmount(interestManagerCharge);
            //4.创建支付手续费流水
            accountFlowService.createPayInterestManageCharge(bidAccount, interestManagerCharge, ps);
            //5.待收本金减少
            bidAccount.setUnReceivePrincipal(bidAccount.getUnReceivePrincipal().subtract(detail.getPrincipal()));
            //6.待收利息减少
            bidAccount.setUnReceiveInterest(bidAccount.getUnReceiveInterest().subtract(detail.getInterest()));

            //7.设置收款计划收款时间
            detail.setPayDate(new Date());
            paymentScheduleDetailService.update(detail);

            //平台
            //1.收到利息管理费
            systemAccount.setUsableAmount(systemAccount.getUsableAmount().add(interestManagerCharge));
            //2.创建收取利息管理费流水
            systemAccountFlowService.createReceiveInterestChargeFlow(systemAccount, interestManagerCharge);

            //特殊情况: 当转让人转让标,但还没有被人认购, 此时借款人还款了:
            //1.判断当前收款计划是否处于转让中
            if (detail.getTransferState() == PaymentScheduleDetail.TRANSFER_STATE_TRANSFERRING) {
                //2.根据这个收款计划查找到对应的债标
                CreditTransfer creditTransfer = creditTransferService.
                        getByBidIdAndTransferFromIdAndState(detail.getBidId(), detail.getToLoginInfoId(), Constants.TRANSFER_STATE_BIDDING);

                //3.修改债权标的状态为撤销
                creditTransfer.setBidRequestState(Constants.TRANSFER_STATE_REVOKE);
                creditTransferService.update(creditTransfer);

                //4.根据这个债权标,找到其下面的所有收款计划,修改转让状态为正常,  bidId, 转让人id, 转让状态
                paymentScheduleDetailService.batchUpdateTransferStateByBidIdAndToLoginInfoId
                        (detail.getBidId(), creditTransfer.getTransFrom().getId(), PaymentScheduleDetail.TRANSFER_STATE_NORMAL);
            }

        }

        //修改平台账户
        systemAccountService.update(systemAccount);

        //统一修改收款人账户
        for (Account account : bidAccounts.values()) {
            accountService.update(account);
        }

        //其他
        //如果获取数据库中已还清的信息条数
        int returnNumber = paymentScheduleService.getCountByStateAndBidRequest(ps.getBidRequestId(), Constants.PAYMENT_STATE_DONE);
        //获取借款对象
        BidRequest br = bidRequestMapper.selectByPrimaryKey(ps.getBidRequestId());
        if (returnNumber == br.getReturnMonthes()) {
            //1.借款对象状态修改为已还清
            br.setBidRequestState(Constants.BIDREQUEST_STATE_COMPLETE_PAY_BACK);
            update(br);

            //2.借款下的投标对象状态修改为已还清
            bidService.batchUpdateStateByBidRequestId(br.getId(), Constants.BIDREQUEST_STATE_COMPLETE_PAY_BACK);

        }
    }

    @Override
    public void publishExpBidRequest(BidRequest bidRequest) {

        //当前发布用户--管理员
        LoginInfo admin = UserContext.getLoginInfo();

        //参数校验

        //1.借款金额要大于等于系统规定的最小金额
        Assert.isFalse(bidRequest.getBidRequestAmount().compareTo(Constants.BORROW_MIN_AMOUNT) < 0,
                "借款不能少于最小金额:" + Constants.BORROW_MIN_AMOUNT);

        //3.利率大于等于系统规定的最小利率
        Assert.isFalse(bidRequest.getCurrentRate().compareTo(Constants.MIN_CURRENTRATE) < 0,
                "最小利率为:" + Constants.MIN_CURRENTRATE);

        //4.利率要小于等于系统规定的最大利率
        Assert.isFalse(bidRequest.getCurrentRate().compareTo(Constants.MAX_CURRENTRATE) > 0,
                "最大利率为:" + Constants.MAX_CURRENTRATE);
        //5.判断借款期限,是否合法
        Assert.isFalse(Constants.BORROW_EXP_RETURN_MONTHS != bidRequest.getReturnMonthes(),
                "非法的借款期限");

        //6.最小投标要大于等于系统规定的最小投标
        Assert.isFalse(bidRequest.getMinBidAmount().compareTo(Constants.BID_MIN_AMOUNT) < 0,
                "投标不能少于最小金额:" + Constants.BID_MIN_AMOUNT);

        //7.最小投标不能超过借款金额的 50%
        BigDecimal tempAmount = bidRequest.getBidRequestAmount().multiply(new BigDecimal(0.5));
        Assert.isFalse(bidRequest.getMinBidAmount().compareTo(tempAmount) > 0,
                "投标不能大于总额的50%:" + tempAmount);

        //8.判断招标天数是否合法
        Assert.isFalse(!Constants.BID_DISABLE_DAYS.contains(bidRequest.getDisableDays().toString()),
                "非法的招标天数");

        //9.判断标题不可为空
        Assert.notNull(bidRequest.getTitle(), "标题不可为空");


        //10.判断还款方式
        Assert.isFalse(bidRequest.getReturnType() != Constants.RETURN_TYPE_MONTH_INTEREST &&
                        bidRequest.getReturnType() != Constants.RETURN_TYPE_MONTH_INTEREST_PRINCIPAL,
                "非法的还款方式");


        //4.保存一个发放体验金对象,new一个对象直接设值

        BidRequest br = new BidRequest();
        br.setReturnType(bidRequest.getReturnType());
        //借款类型--> 体验金  ,  标的状态改为发标待审
        br.setBidRequestType(Constants.BIDREQUEST_TYPE_EXP);
        br.setBidRequestState(Constants.BIDREQUEST_STATE_APPLYING);
        br.setBidRequestAmount(bidRequest.getBidRequestAmount());
        br.setCurrentRate(bidRequest.getCurrentRate());
        br.setMinBidAmount(bidRequest.getMinBidAmount());
        br.setReturnMonthes(bidRequest.getReturnMonthes());
        br.setDisableDays(bidRequest.getDisableDays());
        br.setTitle(bidRequest.getTitle());
        br.setDescription(bidRequest.getDescription());
        br.setCreateUser(admin);
        br.setApplyTime(new Date());
        br.setDescription("叩丁狼体验标");
        //总利息
        BigDecimal totalIntrest = CalculatetUtil.calTotalInterest(br.getReturnType(), br.getBidRequestAmount(),
                br.getCurrentRate(), br.getReturnMonthes());
        br.setTotalRewardAmount(totalIntrest);
        //保存对象
        bidRequestMapper.insert(br);

    }

    @Override
    public PageInfo listForExpPage(ExpAccountQueryObject qo) {
        PageHelper.startPage(qo.getCurrentPage(),qo.getPageSize());
        List list = bidRequestMapper.queryForExpPage(qo);
        return new PageInfo(list);
    }
}
